// 2014年版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 包objfile实现对操作系统特定可执行文件的可移植访问。
package objfile

import (
	"cmd/internal/archive"
	"debug/dwarf"
	"debug/gosym"
	"fmt"
	"io"
	"os"
	"sort"
)

type rawFile interface {
	symbols() (syms []Sym, err error)
	pcln() (textStart uint64, symtab, pclntab []byte, err error)
	text() (textStart uint64, text []byte, err error)
	goarch() string
	loadAddress() (uint64, error)
	dwarf() (*dwarf.Data, error)
}

// 文件是打开的可执行文件。
type File struct {
	r       *os.File
	entries []*Entry
}

type Entry struct {
	name string
	raw  rawFile
}

// 符号是在可执行文件中定义的符号。
type Sym struct {
	Name   string  // 符号名
	Addr   uint64  // 符号的虚拟地址
	Size   int64   // 大小（字节）
	Code   rune    // nm代码（T表示文本，D表示数据等）
	Type   string  // XXX？
	Relocs []Reloc // 按递增顺序
}

type Reloc struct {
	Addr     uint64 // reloc应用于的第一个字节的地址。
	Size     uint64 // 字节数
	Stringer RelocStringer
}

type RelocStringer interface {
	// insnOffset是包含重定位的指令的偏移量
	// 从包含重定位的符号开始。
	String(insnOffset uint64) string
}

var openers = []func(io.ReaderAt) (rawFile, error){
	openElf,
	openMacho,
	openPE,
	openPlan9,
	openXcoff,
}

// 打开打开命名文件。
// 当文件不再需要时，调用方必须调用f.Close。
func Open(name string) (*File, error) {
	r, err := os.Open(name)
	if err != nil {
		return nil, err
	}
	if f, err := openGoFile(r); err == nil {
		return f, nil
	} else if _, ok := err.(archive.ErrGoObjOtherVersion); ok {
		return nil, fmt.Errorf("open %s: %v", name, err)
	}
	for _, try := range openers {
		if raw, err := try(r); err == nil {
			return &File{r, []*Entry{{raw: raw}}}, nil
		}
	}
	r.Close()
	return nil, fmt.Errorf("open %s: unrecognized object file", name)
}

func (f *File) Close() error {
	return f.r.Close()
}

func (f *File) Entries() []*Entry {
	return f.entries
}

func (f *File) Symbols() ([]Sym, error) {
	return f.entries[0].Symbols()
}

func (f *File) PCLineTable() (Liner, error) {
	return f.entries[0].PCLineTable()
}

func (f *File) Text() (uint64, []byte, error) {
	return f.entries[0].Text()
}

func (f *File) GOARCH() string {
	return f.entries[0].GOARCH()
}

func (f *File) LoadAddress() (uint64, error) {
	return f.entries[0].LoadAddress()
}

func (f *File) DWARF() (*dwarf.Data, error) {
	return f.entries[0].DWARF()
}

func (f *File) Disasm() (*Disasm, error) {
	return f.entries[0].Disasm()
}

func (e *Entry) Name() string {
	return e.name
}

func (e *Entry) Symbols() ([]Sym, error) {
	syms, err := e.raw.symbols()
	if err != nil {
		return nil, err
	}
	sort.Sort(byAddr(syms))
	return syms, nil
}

type byAddr []Sym

func (x byAddr) Less(i, j int) bool { return x[i].Addr < x[j].Addr }
func (x byAddr) Len() int           { return len(x) }
func (x byAddr) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

func (e *Entry) PCLineTable() (Liner, error) {
	// 如果原始文件直接实现了Liner，请使用它。
	// 目前，只有Go中间对象和归档（goobj）使用此路径。
	if pcln, ok := e.raw.(Liner); ok {
		return pcln, nil
	}
	// 否则，请阅读pcln表并用其构建一个线性关系。
	textStart, symtab, pclntab, err := e.raw.pcln()
	if err != nil {
		return nil, err
	}
	return gosym.NewTable(symtab, gosym.NewLineTable(pclntab, textStart))
}

func (e *Entry) Text() (uint64, []byte, error) {
	return e.raw.text()
}

func (e *Entry) GOARCH() string {
	return e.raw.goarch()
}

// LoadAddress返回文件的预期加载地址。
// 这与独立位置的实际加载地址不同
// 可执行文件。
func (e *Entry) LoadAddress() (uint64, error) {
	return e.raw.loadAddress()
}

// DWARF返回文件的DWARF调试数据（如果有）。
// 这是cmd/pprof用来定位cgo函数的。
func (e *Entry) DWARF() (*dwarf.Data, error) {
	return e.raw.dwarf()
}
